home *** CD-ROM | disk | FTP | other *** search
/ PsL Monthly 1993 December / PSL Monthly Shareware CD-ROM (December 1993).iso / prgmming / dos / pascal / heap55.com / HEAP.DOC < prev    next >
Encoding:
Text File  |  1990-01-07  |  17.5 KB  |  409 lines

  1.               Advanced Heap Management for Turbo Pascal 5.5
  2.                             Version 5.5
  3.                           January 6, 1990
  4.  
  5. Overview
  6. --------------------------------------------------------------------------
  7. Turbo Pascal's heap is one of the most useful and powerful features of the
  8. language. By using the heap, programs can access all 640K of DOS memory in a
  9. completely dynamic fashion. With power comes responsibility, however. Managing
  10. pointers to the heap is one of the trickiest subjects in Pascal. And as time
  11. goes by, the 640K of DOS memory doesn't seem all that big -- accessing even
  12. more memory is desirable.
  13.  
  14. This collection of Turbo Pascal units and utilities offers methods for
  15. managing and extending the heap. It includes the following:
  16.  
  17.   o a patch to TPC.EXE so that the compiler will generate an interrupt to a
  18.     user-supplied routine after dereferencing each pointer.
  19.   o a unit that checks for dereferencing an invalid pointer.
  20.   o a unit that transfers control of the New, Getmem, Dispose, and FreeMem
  21.     procedures to user-supplied routines.
  22.   o a unit that logs various information about the heap to disk at
  23.     convenient points in a program.
  24.  
  25. This collection of utilities is an updated version of those previously
  26. written for Turbo Pascal 4.0 and 5.0.
  27.  
  28. Several people have developed virtual heap managers using earlier versions of
  29. the compiler patch. This version of the patch should allow those utilities to
  30. work with Turbo Pascal 5.5. Be careful to note the version 5.5 specific
  31. comments in the documentation below, however. You can find these comments by
  32. searching for the string Note!
  33.  
  34. Patching TPC.EXE
  35. --------------------------------------------------------------------------
  36. The patching program HPAT55 makes a small but important change to TPC.EXE
  37. (version 5.5 only). Once the change is in place, the compiler is capable of
  38. generating an interrupt each time a pointer is dereferenced. When the
  39. interrupt occurs, a user-supplied routine takes control, thus allowing a
  40. program to validate or modify the pointer value.
  41.  
  42. To apply the patch, compile the supplied program HPAT55.PAS, and run it while
  43. a copy of TPC.EXE is in the current directory. HPAT55 modifies about 70
  44. bytes of the command line compiler (most of which are required to relocate a
  45. particular table so that it can hold two more bytes). Note that the patcher
  46. works only on TPC, not on TURBO.EXE. While it would be possible to make the
  47. same patch to TURBO.EXE, we rarely use the integrated compiler ourselves, and
  48. it is linked differently enough to make finding the patch points a
  49. time-consuming chore.
  50.  
  51. HPAT55 assures that the compiler version you have is Turbo Pascal 5.50. If
  52. the file size is wrong, or if any of the locations to be patched contain
  53. unexpected data, HPAT55 will halt with an error message. If this happens, be
  54. sure to restore a clean copy of TPC.EXE from a backup, since a partial patch
  55. may have occurred.
  56.  
  57. After the patch is successfully completed, the compiler's behavior is changed
  58. in the following manner:
  59.  
  60.   0. The compiler version will be reported as "5.5p" whenever TPC writes its
  61.   copyright message.
  62.  
  63.   1. A new compiler switch directive will exist: {$P+} or {$P-}. The default
  64.   state of this switch is {$P-}. When the switch is turned ON, the compiler
  65.   will generate an interrupt $66 after each pointer dereference. The switch
  66.   may be turned on and off as desired in the source code only (not at the DOS
  67.   command line nor in a TPC.CFG file). Like other Turbo compiler directives,
  68.   the switch returns to its default state at the beginning of each unit.
  69.  
  70.   It is your program's responsibility to install an interrupt handler prior to
  71.   the first occurrence of int $66 in the program. The supplied unit BADPTR
  72.   automatically installs such a handler for you.
  73.  
  74.   Interrupt $66 is one of the user-definable interrupts described by IBM. If
  75.   this interrupt conflicts with your application, you may change HPAT55.PAS to
  76.   use a different interrupt. To do so, simply change the constant
  77.   DerefInterrupt in HPAT55.PAS, recompile the program and patch a fresh copy
  78.   of the compiler. User-definable interrupts range from $60 to $66. ($67 is
  79.   used for EMS, so it should be avoided here.)
  80.  
  81.   If you change the interrupt, be sure to modify any related interrupt handler
  82.   (such as the one in BADPTR) as well.
  83.  
  84.   2. Upon entry to your interrupt handler, ES:DI will hold the current value
  85.   of the pointer being dereferenced. The interrupt handler must preserve the
  86.   values of AX, BX, CX, DX, SI, DS, SS, BP, and SP, and it must return either
  87.   the same value of ES:DI or a value appropriately mapped by a virtual memory
  88.   manager.
  89.  
  90. While a Turbo Pascal Interrupt procedure will work correctly for the pointer
  91. handler, the performance penalty may be unacceptable. For best performance,
  92. the int $66 handler should be written primarily in assembly language. See
  93. BADPTR.ASM for a simple example of such a handler.
  94.  
  95. The following fragment shows the code generated by the patched compiler when a
  96. pointer P is a global variable, in this case a pointer to an integer.
  97.  
  98.                  P^ := 1;
  99.         C43E0000       LES    DI,[P]                ;load pointer into ES:DI
  100.         CD66           INT    66                    ;call user routine
  101.         26C7050100     MOV    WORD PTR ES:[DI],0001 ;assign value
  102.  
  103. An easy rule to remember is this: for each appearance of a dereferencing caret
  104. (^) in your source code, an interrupt will be generated. Of course, this
  105. occurs only when the $P+ directive is in effect for that statement.
  106.  
  107. Note!
  108. In version 5.5 of Turbo Pascal, a dereferencing interrupt occurs in one
  109. additional situation that is related to object-oriented programming. The
  110. following code generates an interrupt:
  111.  
  112.     dispose(anobjectptr, done);
  113.  
  114. This makes sense, since the statement is a kind of shorthand for the
  115. following:
  116.  
  117.     anobjectptr^.done;
  118.     dispose(anobjectptr);
  119.  
  120. The code that actually calls dispose in this case is located within the done
  121. destructor, but the effect is the same.
  122.  
  123.  
  124. Using BADPTR
  125. --------------------------------------------------------------------------
  126. BADPTR is a unit that works in conjunction with the HPAT55-patched compiler.
  127. If you compile with the patched compiler, you must be sure that you USE the
  128. unit BADPTR (or another unit with an int $66 handler), or your program may
  129. crash unexpectedly. (Interrupt $66 often points to an IRET by default, but
  130. that is not always the case.) You should USE BADPTR early in the USES
  131. statement of the main program. If you USE BADPTR and then don't compile with
  132. the patched compiler, no harm will result.
  133.  
  134. BADPTR automatically installs an interrupt handler that is invoked whenever a
  135. pointer is dereferenced and the $P+ directive is active. This interrupt
  136. handler checks that the pointer refers to the normal Turbo Pascal heap,
  137. between HeapOrg and the top of the free list. If the pointer falls into this
  138. range, the interrupt handler returns and the program proceeds. If the pointer
  139. is outside of the normal heap, BADPTR calls an error routine, which writes the
  140. value of the pointer as well as the relative code address where the error
  141. occurred, and then halts. Note that NIL pointers will always fail BADPTR's
  142. test. You can use Turbo's find runtime error facility to correlate the error
  143. address to a source position.
  144.  
  145. If you are using pointers not allocated by New or GetMem, there are two ways
  146. to keep BADPTR from reporting a false error. A common example of such a
  147. pointer would be one pointing to the DOS command line, initialized with P :=
  148. Ptr(PrefixSeg, $80). First, you could assure that the $P+ directive is not
  149. active wherever you dereference such a pointer. Second, BADPTR interfaces two
  150. WORD variables that allow you to determine the acceptable range for pointers.
  151. HeapBot is the lowest acceptable pointer segment, and HeapTop is the highest.
  152. BADPTR initializes these to the range of the normal Turbo heap. Suppose you
  153. wanted to accept any pointer but the NIL pointer. In that case you could
  154. assign:
  155.  
  156.   HeapBot := $0001;
  157.   HeapTop := $FFFF;
  158.  
  159. BADPTR uses simple WriteLn statements to report an error. If this technique is
  160. not appropriate for a particular application, modify the procedure BadPointer
  161. in BADPTR.PAS. Upon entry to BadPointer, the system variable ErrorAddr
  162. contains the PSP-relative address of the code causing the error, and BadP
  163. contains the faulty pointer. Generally, BadPointer should halt without
  164. returning. If it does not halt, execution will continue after the interrupt,
  165. using the pointer originally supplied in ES:DI. The BadPointer procedure
  166. cannot change the actual pointer value.
  167.  
  168. If you change HPAT55 to use a different interrupt, be sure to make the
  169. corresponding change in BADPTR as well.
  170.  
  171.  
  172. Using GRABHEAP
  173. --------------------------------------------------------------------------
  174. GRABHEAP is a unit that allows a program to take control of memory allocation
  175. and deallocation functions normally handled by the system unit: NEW, GETMEM,
  176. DISPOSE, and FREEMEM. To offer complete control, GRABHEAP interfaces two
  177. procedures:
  178.  
  179.   procedure CustomHeapControl(GetPtr : GetMemFunc; FreePtr : FreeMemProc);
  180.     {-Give control of GetMem, New, FreeMem, Dispose to specified procedures}
  181.  
  182.   procedure SystemHeapControl;
  183.     {-Restore control to the system heap routines}
  184.  
  185. A program can call CustomHeapControl to transfer control of the system
  186. routines to specified procedures. To do so, pass the addresses of two FAR,
  187. global procedures that match the following declarations:
  188.  
  189.   {$F+}
  190.   function CustomGetMem(Size : Word) : pointer;
  191.   begin
  192.     ...
  193.   end;
  194.   procedure CustomFreeMem(P : Pointer; Size : Word);
  195.   begin
  196.     ...
  197.   end;
  198.   {$F-}
  199.  
  200. To set up for this example, an appropriate call would be
  201.  
  202.   CustomHeapControl(CustomGetMem, CustomFreeMem);
  203.  
  204. Note!
  205. Due to changes in Turbo Pascal 5.5's heap manager, the declarations for the
  206. custom routines are different than for previous versions of these utilities.
  207.  
  208. Thereafter, any calls that would normally go to New or GetMem are transferred
  209. to CustomGetMem, and calls for Dispose or FreeMem are sent to CustomFreeMem.
  210.  
  211. The custom heap management routines can perform any needed actions, including
  212. calls to the original system GetMem and FreeMem routines. To call the original
  213. routines, the program must temporarily restore control to the system runtime
  214. library by calling GRABHEAP's SystemHeapControl routine.
  215.  
  216. Here is an example of CustomGetMem and CustomFreeMem routines that do nothing
  217. but keep a balance sheet of the memory allocated and deallocated in a program:
  218.  
  219.   var
  220.     TotalAlloc : LongInt;
  221.     HeapMax : Pointer;
  222.  
  223.   {$F+}
  224.   procedure MyFree(var P : Pointer; Size : Word); forward;
  225.  
  226.   function MyGet(Size : Word) : pointer;
  227.   var
  228.     P : pointer;
  229.   begin
  230.     Inc(TotalAlloc, Size);              {Update balance sheet}
  231.     SystemHeapControl;                  {Give back heap control temporarily}
  232.     GetMem(P, Size);                    {Use the system routine to allocate}
  233.     MyGet := P;                         {Assign it to function result}
  234.     if LongInt(HeapPtr) > LongInt(HeapMax) then
  235.       HeapMax := HeapPtr;               {Keep track of heap high water mark}
  236.     CustomHeapControl(MyGet, MyFree);   {Take over heap control again}
  237.   end;
  238.  
  239.   procedure MyFree(P : Pointer; Size : Word);
  240.   begin
  241.     Dec(TotalAlloc, Size);
  242.     SystemHeapControl;
  243.     FreeMem(P, Size);
  244.     CustomHeapControl(MyGet, MyFree);
  245.   end;
  246.   {$F-}
  247.  
  248.   begin
  249.     TotalAlloc := 0;                           {No memory allocated to start}
  250.     HeapMax := HeapOrg;                        {High water mark at base of heap}
  251.     CustomHeapControl(MyGet, MyFree);          {Take over heap control}
  252.  
  253.     ....                                       {Normal program actions}
  254.  
  255.     WriteLn('Maximum heap usage: ',
  256.             16*(LongInt(seg(HeapMax^))-seg(HeapOrg^)), ' bytes');
  257.     if TotalAlloc <> 0 then
  258.       WriteLn('Allocated memory not freed: ', TotalAlloc, ' bytes');
  259.   end.
  260.  
  261. GRABHEAP can be put to more powerful uses, of course. For example, based on an
  262. installation flag, it could select among data storage in normal, EMS, or disk
  263. memory. The pointer returned by the custom GetMem routine need not be a normal
  264. pointer, but can instead be an EMS page and offset, or a disk page and offset,
  265. combined into a four byte record. Then, based on the same installation flag, a
  266. deref-interrupt handler can access the data on the appropriate media and
  267. return a pointer to the actual data buffered in memory.
  268.  
  269. Note!
  270. In Turbo Pascal 5.5, there is one form of call to New() that won't be
  271. redirected to the custom routine you install. That's a call to allocate an
  272. object and call its constructor at the same time:
  273.  
  274.   new(AnObjP, Init);
  275.  
  276. or
  277.  
  278.   AnObjP := new(AnObjPtrType, Init);
  279.  
  280. In both of these cases the allocation is performed by code within the
  281. constructor itself. GRABHEAP doesn't redirect this code to the custom routines
  282. since the code must perform special checks related to inheritance.
  283.  
  284.  
  285. Using HEAPLOG
  286. -------------------------------------------------------------------------
  287. HEAPLOG is a unit that builds on top of the GRABHEAP facility to provide
  288. diagnostic information for programs that make extensive use of the heap. It
  289. keeps a log of all heap allocation and deallocation, and allows the program to
  290. dump this log to disk at any time. By studying the log, you can detect
  291. excessive heap fragmentation and memory that hasn't been deallocated.
  292.  
  293. You'll get a basic log just by using HEAPLOG in your program. HEAPLOG creates
  294. a file named HEAP.LOG. When a program first starts, a report labeled "Initial"
  295. is written to HEAP.LOG. When the program ends, a report labeled "Final" is
  296. written to HEAP.LOG. The reports themselves will be described momentarily.
  297.  
  298. HEAPLOG interfaces two procedures that control the logging process:
  299.  
  300. procedure DumpHeapLog(Msg : string);
  301.   {-Write the current heap log to a file}
  302.  
  303. procedure ClearLog;
  304.   {-Clear all entries from the log}
  305.  
  306. DumpHeapLog adds another report to HEAP.LOG, giving it the label passed in
  307. Msg. ClearLog clears HEAPLOG's internal data structures so that succeeding
  308. reports will be relative to that point instead of relative to the beginning of
  309. the program.
  310.  
  311. The following is an example of a HEAPLOG report.
  312.  
  313. Initial
  314.  
  315. MemAvail: 385568
  316. MaxAvail: 385568
  317. HeapPtr : 41DE:0000
  318. HeapCnt : 0
  319. FreeCnt : 0
  320. Filled  : FALSE
  321.  
  322. Intermediate
  323.  
  324. MemAvail: 382017
  325. MaxAvail: 380742
  326. HeapPtr : 4309:0002
  327. HeapCnt : 15
  328. FreeCnt : 5
  329. Filled  : FALSE
  330.  
  331.   Pointer   Size  Allocated at
  332. 42CF:0008    115  0000:0AE3
  333. 42E9:000F    499  0000:0AE3
  334. 41F8:000F    387  0000:07F1
  335. 4229:0004    396  0000:07F1
  336. 4259:0007    125  0000:07F1
  337. 4261:0004    415  0000:07F1
  338. 427B:0003    116  0000:07F1
  339. 4282:0007    269  0000:07F1
  340. 4293:0004    223  0000:07F1
  341. 42A4:0009    158  0000:07F1
  342. 42AE:0007     42  0000:07F1
  343. 42B1:0001    112  0000:07F1
  344. 42B8:0001     74  0000:07F1
  345. 42BC:000B    301  0000:07F1
  346. 42D8:0008    279  0000:07F1
  347.  
  348. Free start  Size
  349. 42D6:000B     29
  350. 42A1:0003     54
  351. 4242:0000    375
  352. 4211:0002    386
  353. 41DE:0000    431
  354.  
  355. Final
  356.  
  357. MemAvail: 385568
  358. MaxAvail: 385568
  359. HeapPtr : 41DE:0000
  360. HeapCnt : 0
  361. FreeCnt : 0
  362. Filled  : FALSE
  363.  
  364. HEAPLOG always generates the Initial and Final reports. For programs that
  365. deallocate all their dynamic memory, the Final report should be the same as
  366. the Initial. In the example, the Intermediate report is one generated by
  367. calling DumpHeapLog directly.
  368.  
  369. Each report shows the values of MemAvail and MaxAvail in bytes, and the
  370. current heap high water mark, HeapPtr. Each also shows the number of separate
  371. blocks allocated on the heap (HeapCnt) and the number of blocks deallocated
  372. and currently on the free list (FreeCnt). If HeapCnt is non-zero, HEAPLOG
  373. shows the value of each allocated pointer, the size of the region it points
  374. to, and the code address where the pointer was allocated. If FreeCnt is
  375. non-zero, HEAPLOG shows the starting address of each free block and its size.
  376.  
  377. By default, HEAPLOG can track the allocation of up to 1000 pointers in one
  378. run. If the program allocates more than this at one time, the Filled field
  379. will report True. HEAPLOG's capacity can be adjusted by modifying the constant
  380. MaxLog in HEAPLOG.PAS. Note that HEAPLOG itself uses 10 bytes of heap space
  381. for each increment in MaxLog. (HEAPLOG doesn't report its own heap usage,
  382. however.) The performance of calls to GetMem, FreeMem, New, and Dispose will
  383. degrade for large numbers of pointers.
  384.  
  385. Note!
  386. Because of the GRABHEAP limitation already described, HEAPLOG won't report
  387. dynamic instances of objects that are allocated and initialized with a single
  388. call to New(Obj, Init).
  389.  
  390. Legalities
  391. --------------------------------------------------------------------------
  392. Chris Franzen of O.K.Soft, West Germany, used earlier versions of HEAP.ARC to
  393. help track down the compiler patch locations for Turbo Pascal 5.5. Thanks to
  394. Chris for spending the time to do this.
  395.  
  396. Other programs and documentation in this package are copyright (C) TurboPower
  397. Software, 1988, 1989, 1990. All rights reserved. TurboPower Software hereby
  398. grants permission for free distribution of this software, and for use of these
  399. units and techniques within commercial and non-commercial applications. The
  400. units and utilities themselves may not be distributed commercially without
  401. obtaining written permission from TurboPower Software.
  402.  
  403. We would appreciate hearing about enhancements made to these routines. Contact
  404. Kim Kokkonen at Compuserve ID 76004,2611 or write to:
  405.  
  406.   TurboPower Software
  407.   P.O. Box 66747
  408.   Scotts Valley, CA 95066
  409.